/*
Copyright (C) 2016  Marien Raat

This program is free software: you can redistribute it and/or modify
it under the terms of the GNU Affero General Public License as
published by the Free Software Foundation, either version 3 of the
License, or (at your option) any later version.

This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
GNU Affero General Public License for more details.

You should have received a copy of the GNU Affero General Public License
along with this program.  If not, see <http://www.gnu.org/licenses/>.
*/

#include "car.h"
#include "config.h"

#include <math.h>
#include <iostream>

extern float timeStep;

Car::Car()
{

}

Car::Car(b2World *world)
{
    initialize(world);
}

void Car::initialize(b2World *world)
{
    this->world = world;

    power = turning = 0.0f;
    drifting = boosting = false;
    boostUsed = 0.0f;
    boostLeft = 30.0f;
    dashDirection = -1;

    inWorld = false;

    createBodyDefinitions();
}

void Car::updateFromPacket(sf::Packet &packet)
{
    sf::Vector2f newPosition;
    float newRotation;
    bool newInWorld;
    b2Vec2 linearVelocity;
    float angularVelocity;

    packet >> newPosition.x >> newPosition.y >> newRotation
           >> newInWorld >> linearVelocity.x >> linearVelocity.y
           >> angularVelocity >> power >> turning >> boosting
           >> drifting >> frontLeftWheel >> frontRightWheel
           >> backLeftWheel >> backRightWheel >> boostLeft
           >> dashDirection >> dashingTimeLeft >> nextDashTimeLeft;

    setInWorld(newInWorld);
    setPosition(newPosition);
    setRotation(newRotation);
    if (inWorld) {
        body->SetLinearVelocity(linearVelocity);
        body->SetAngularVelocity(angularVelocity);
    }
}

void Car::saveToPacket(sf::Packet &packet)
{
    sf::Vector2f position = getPosition();
    b2Vec2 linearVelocity = inWorld ? body->GetLinearVelocity()
        : b2Vec2(0, 0);
    float angularVelocity = inWorld ? body->GetAngularVelocity()
        : 0.0f;

    packet << position.x << position.y << getRotation() << inWorld
           << linearVelocity.x << linearVelocity.y << angularVelocity
           << power << turning << boosting << drifting
           << frontLeftWheel << frontRightWheel
           << backLeftWheel << backRightWheel << boostLeft
           << dashDirection << dashingTimeLeft << nextDashTimeLeft;
}

void Car::setTextures(sf::Texture *carTexture,
                      sf::Texture *exhaustTexture)
{
    this->carTexture = carTexture;
    carSprite.setTexture(*carTexture);
    sf::Vector2u textureSize = carTexture->getSize();
    carSprite.setOrigin(textureSize.x / 2.0f, textureSize.y / 2.0f);
    carExhaust.setTexture(exhaustTexture);
}

void Car::setInWorld(bool inWorld)
{
    if (inWorld != this->inWorld) {
        if (inWorld) {
            body = world->CreateBody(&bodyDef);
            fixtureDef.shape = &bodyShape;
            b2Fixture *fixture = body->CreateFixture(&fixtureDef);

            frontLeftWheel.initialize
                (body, b2Vec2(45.0f / SCALE_F, -22.5 / SCALE_F));
            frontRightWheel.initialize
                (body, b2Vec2(45.0f / SCALE_F, +22.5 / SCALE_F));
            backLeftWheel.initialize
                (body, b2Vec2(-45.0f / SCALE_F, -22.5 / SCALE_F));
            backRightWheel.initialize
                (body, b2Vec2(-45.0f / SCALE_F, +22.5 / SCALE_F));
        } else {
            world->DestroyBody(body);
        }
    }
    this->inWorld = inWorld;
}

void Car::setPosition(sf::Vector2f position)
{
    if (!inWorld) return;
    position /= (float) SCALE;
    b2Vec2 bodyPosition(position.x, position.y);
    body->SetTransform(bodyPosition, body->GetAngle());
}

sf::Vector2f Car::getPosition()
{
    if (!inWorld) return sf::Vector2f();
    b2Vec2 bodyPosition = body->GetPosition();
    return sf::Vector2f(SCALE * bodyPosition.x,
                        SCALE * bodyPosition.y);
}

void Car::setRotation(float rotationInDegrees)
{
    if (!inWorld) return;
    body->SetTransform(body->GetPosition(),
                       DEG_TO_RAD * rotationInDegrees);
}

float Car::getRotation()
{
    if (!inWorld) return 0.0f;
    return RAD_TO_DEG * body->GetAngle();
}

void Car::update()
{
    if (!inWorld) return;

    applyMovement();

    carSprite.setPosition(getPosition());
    carSprite.setRotation(getRotation());

    bool dashing = dashingTimeLeft > 0;
    frontLeftWheel.update(drifting, dashing);
    frontRightWheel.update(drifting, dashing);
    backLeftWheel.update(drifting, dashing);
    backRightWheel.update(drifting, dashing);

    if (boostLeft < 100.0f)
        boostLeft += 5.0f * timeStep;

    if (client) {
        sf::Vector2f position = getPosition() -
            sf::Vector2f(45 * cos(getRotation() * DEG_TO_RAD),
                         45 * sin(getRotation() * DEG_TO_RAD));
        sf::Vector2f velocity(body->GetLinearVelocity().x * SCALE,
                              body->GetLinearVelocity().y * SCALE);
        carExhaust.update(position,
                          velocity,
                          (getRotation() - 180) * DEG_TO_RAD,
                          boostUsed);
    }

    boostUsed = 0.0f;
    dashingTimeLeft -= timeStep;
    nextDashTimeLeft -= timeStep;
}

void Car::draw(sf::RenderTarget *target)
{
    if (!inWorld) return;
    carSprite.setTexture(*carTexture);
    target->draw(carExhaust);
    target->draw(carSprite);
}

void Car::applyMovement()
{
    if (!inWorld) return;

    float power = this->power > 0 ? this->power : this->power * 0.8f;
    if (dashingTimeLeft > 0) power = 1.0f;
    frontLeftWheel.move(power);
    frontRightWheel.move(power);
    backLeftWheel.move(power);
    backRightWheel.move(power);

    frontLeftWheel.turn(turning);
    frontRightWheel.turn(turning);

    // Apply boost
    if (boosting && boostLeft > 0.0f) {
        boostUsed = std::min(boostLeft, 25.0f * timeStep) /
            (25.0f * timeStep);
        boostLeft -= boostUsed * 25.0f * timeStep;
        body->ApplyForce(
            ROTATE_B2VEC2(b2Vec2(40.0f * boostUsed, 0.0f),
                          body->GetAngle()),
            body->GetWorldCenter(), true);
    }

    // Apply dash
    if (dashDirection >= 0 && nextDashTimeLeft <= 0.0f) {
        dash();
    }
    dashDirection = -1;
}

void Car::dash()
{
    b2Vec2 impulse = ROTATE_B2VEC2(b2Vec2(30.0f, 0.0f),
                                   ((dashDirection * 90) +
                                    getRotation())* DEG_TO_RAD);
    body->ApplyLinearImpulse(impulse, body->GetWorldCenter(), true);
    dashingTimeLeft = 0.4f;
    nextDashTimeLeft = 0.8f;
}

void Car::createBodyDefinitions()
{
    bodyDef.linearDamping = 1.0f;
    bodyDef.angularDamping = 4.0f;
    bodyDef.type = b2_dynamicBody;

    bodyShape.SetAsBox(50.0f / SCALE_F, 25.0f / SCALE_F);

    fixtureDef.shape = &bodyShape;
    fixtureDef.density = 1.0f;
    fixtureDef.friction = 0.3f;
}

sf::Packet &operator <<(sf::Packet &packet, Car &car)
{
    car.saveToPacket(packet);
    return packet;
}

sf::Packet &operator >>(sf::Packet &packet, Car &car)
{
    car.updateFromPacket(packet);
    return packet;
}
